# -*- coding: utf-8 -*-
import logging
from pylons import cache
from pythonblog.model import meta
from pythonblog.model.content import *
from pythonblog.service import user_service
import pythonblog.lib.helpers as h
from sqlalchemy.sql import select, func, and_
from bisect import insort_left
import datetime

log = logging.getLogger(__name__)

_date_format = '%Y-%m-%d'

_table = Post.__table__
_column = _table.c

_comment_table = Comment.__table__
_comment_column = _comment_table.c

_visitor_table = VisitorTrace.__table__
_visitor_column = _visitor_table.c


initailized = False
_posts_cache = {}        # key: post_id, value: PostInfo
#_new_posts_max = 20
_new_posts = []          # list of PostInfo sorted by createDate desc
_section_first_post = {} # key: section_id, value: PostInfo

global_cache = cache.get_cache('content.global', type="memory")

def get_comment_count(post_id):
    if not _comments_cache.has_key(post_id):
        _load_comments_from_db(post_id)

    return len(_comments_cache[post_id])

class PostInfo(object):

    def get_createDate_only(self):
        if self.createDate:
            return datetime.datetime.strftime(self.createDate, _date_format)
        return ''

    createDate_only = property(get_createDate_only)

    def get_updateDate_only(self):
        if self.updateDate:
            return datetime.datetime.strftime(self.updateDate, _date_format)
        return ''

    updateDate_only = property(get_updateDate_only)

    def get_user(self):
        return user_service.get_user_by_id(self.user_id)

    user = property(get_user)

    def get_comments_count(self):
        return get_comment_count(self.id)

    comments_count = property(get_comments_count)

    def get_promotes_count(self):
        return get_promote_count(self.id)

    promotes_count = property(get_promotes_count)

    def get_demotes_count(self):
        return get_demote_count(self.id)

    demotes_count = property(get_demotes_count)

    def get_total_scroes(self):
        scores = self.viewTimes + self.comments_count * 3 + self.promotes_count * 2 - self.demotes_count
        return u'总分: %s'%scores

    def get_total_post_hint(self):
        return u'浏览(1分):%s   评论(3分):%s   顶(2分):%s   踩(-1分):%s'%(self.viewTimes, self.comments_count, self.promotes_count, self.demotes_count)

    def get_dig_extra(self):
        visit = get_latest_visit(self.id)
        if visit:
            return visit.get_extra()
        return ''

    def get_comment_extra(self):
        comments = get_comments(self.id)
        info = ''
        #if len(comments) > 1: why???????
        if len(comments):
            info = comments[0].get_extra()
        return info
    
    def __cmp__(self, c):
        return -cmp(self.createDate, c.createDate)


def _copy_post(post_info, post):
    post_info.id = post.id
    post_info.title = post.title
    post_info.abstract = post.abstract
    post_info.content = post.content
    post_info.createDate = post.createDate
    post_info.updateDate = post.updateDate
    post_info.status = post.status
    post_info.type = post.type
    post_info.section_id = post.section_id
    post_info.user_id = post.user_id
    post_info.viewTimes = post.viewTimes
    post_info.tags_display = post.tags_display

def add_or_update_post_to_cache(post):
    if not _posts_cache.has_key(post.id):
        _posts_cache[post.id] = PostInfo()
        _add_to_new_posts(post.section_id, _posts_cache[post.id])
    _copy_post(_posts_cache[post.id], post)
    return _posts_cache[post.id]

def _add_to_new_posts(section_id, post_info):
    _new_posts.insert(0, post_info)
    _section_first_post[section_id] = post_info

"""
def add_posts_to_cache(posts):
    post_infos = []
    for post in posts:
        post_infos.append(add_post_to_cache(post))
    return post_infos
    


def get_all_section_posts_except_first(section_id):
    if not _section_posts.has_key(section_id):
        return []
    posts = list(_section_posts[section_id])
    first_post = get_section_first_post(section_id)
    if first_post in posts:
        posts.remove(first_post)
    return posts

def get_section_posts_except_first(section_id, limit=5):
    if not _section_posts.has_key(section_id):
        return []
    posts = _section_posts[section_id][:(limit+1)]
    first_post = get_section_first_post(section_id)
    if first_post in posts:
        posts.remove(first_post)
    else:
        posts = posts[:limit]
    return posts
"""

def get_section_first_post(section_id):
    if _section_first_post.has_key(section_id):
        return _section_first_post[section_id]
    return None


class BlogInfo(object):
    pass


def get_blog_info(user_id):
    blogInfo = BlogInfo()    
    s = select([_column.status, _column.section_id, func.count(_column.id), func.sum(_column.viewTimes)], _column.user_id==user_id).group_by(_column.status, _column.section_id)
    draftNumber = 0
    postNumber = 0
    viewTimes = 0
    sectionNumbers = {}
    for status, section_id, count, viewTime in meta.engine.execute(s).fetchall():
        if status == 1:
            postNumber += count
            sectionNumbers[section_id] = count
            viewTimes += viewTime
        else:
            draftNumber += count        
    blogInfo.draftNumber = draftNumber
    blogInfo.postNumber = postNumber
    blogInfo.viewTimes = viewTimes
    blogInfo.sectionNumbers = sectionNumbers
    blogInfo.topPosts = _get_top_posts(user_id)
    return blogInfo

def _get_top_posts(user_id):
    user_top_posts = cache.get_cache('user.top.posts', type="memory")
    return user_top_posts.get_value(key=user_id,
                                    createfunc=lambda: _query_top_posts(10, user_id),
                                    expiretime=300)

def get_posts(user_id, section_id = None):
    s = select([_column.id], _column.status==1)
    s = s.where(_column.user_id==user_id)
    if section_id:
        s = s.where(_column.section_id==section_id)
    s = s.order_by(_column.updateDate.desc())
    return s

def get_news():
    def _query_news():
        s = select([_column.id, _column.title, _column.user_id], and_(_column.status==1, _column.type==3)).order_by(_column.createDate.desc()).limit(20)
        return meta.engine.execute(s).fetchall()

    return global_cache.get_value(key='news',
                                    createfunc=lambda: _query_news(),
                                    expiretime=300)


def _query_top_posts(limit=None, user_id=None):
    """
    rank: viewTimes + (promote.count - demote.count) * 2 + comment.count * 3

    old:
    s = select([_column.id, '(post.viewTimes + sum(CASE visitor_trace.promote WHEN 1 THEN 2 when 0 then -1 else 0 end) + count(comment.id) * 3) scores'], _column.status==1, from_obj=[_table.outerjoin(_comment_table).outerjoin(_visitor_table)])
    if user_id:
        s = s.where(_column.user_id==user_id)
    s = s.group_by(_column.id)
    s = s.order_by('(post.viewTimes + sum(CASE visitor_trace.promote WHEN 1 THEN 2 when 0 then -1 else 0 end) + count(comment.id) * 3) desc', _column.updateDate.desc())
    if limit:
        s = s.limit(limit)
    return meta.engine.execute(s).fetchall()
    """

    s = """
    select a.id, sum(scores) scores from (
    SELECT post.id, post.user_id, sum(CASE visitor_trace.promote WHEN 1 THEN 2 when 0 then -1 else 0 end) scores
    FROM post LEFT OUTER JOIN visitor_trace ON post.id = visitor_trace.post_id
    WHERE post.status = 1
    GROUP BY post.id
    union all
    SELECT post.id, post.user_id, (post.viewTimes + count(comment.id) * 3) scores
    FROM post LEFT OUTER JOIN comment ON post.id = comment.post_id
    WHERE post.status = 1
    GROUP BY post.id
    ) a
    """
    if user_id:
        s = s + ' where a.user_id = %s '%user_id
    s = s + ' group by a.id order by sum(scores) desc '
    if limit:
        s = s + ' limit %s '%limit
    return meta.engine.execute(s).fetchall()

def _query_recommand_posts(limit=20):
    s = select(['post_id'], from_obj=['recommand_post']).distinct()
    s = s.limit(limit)
    s = s.order_by('id desc')
    ids = []
    for id in meta.engine.execute(s).fetchall():
        ids.append(id[0])
    return ids

def _query_hot_posts():
    """
    rank: newly commented or digged
    
    s = select([_column.id], _column.status==1, from_obj=[_table.outerjoin(_comment_table).outerjoin(_visitor_table)]).distinct()
    s = s.order_by(_comment_column.createDate.desc(), _visitor_column.createDate.desc())
    ids = []
    for id in meta.engine.execute(s).fetchall():
        ids.append(id[0]) 
    return ids

    """
    
    s = r"""
    SELECT post.id, max(comment.`createDate`) c_date, max(visitor_trace.`createDate`) v_date FROM post
    LEFT OUTER JOIN comment ON post.id = comment.post_id
    LEFT OUTER JOIN visitor_trace ON post.id = visitor_trace.post_id
    WHERE post.status = 1
    group by post.id
    order by IF(max(IFNULL(comment.`createDate`, STR_TO_DATE('00/00/0000', '%%m/%%d/%%Y'))) > max(IFNULL(visitor_trace.`createDate`, STR_TO_DATE('00/00/0000', '%%m/%%d/%%Y'))),
    max(IFNULL(comment.`createDate`, STR_TO_DATE('00/00/0000', '%%m/%%d/%%Y'))),
    max(IFNULL(visitor_trace.`createDate`, STR_TO_DATE('00/00/0000', '%%m/%%d/%%Y')))) desc,
    post.createDate desc
    """
    return meta.engine.execute(s).fetchall()

def get_top_posts():
    return global_cache.get_value(key='top.posts',
                                    createfunc=lambda: _query_top_posts(),
                                    expiretime=300)

def get_recommand_posts():
    return global_cache.get_value(key='recommand.posts',
                                    createfunc=lambda: _query_recommand_posts(),
                                    expiretime=86400)

def get_hot_posts():
    return global_cache.get_value(key='hot.posts',
                                    createfunc=lambda: _query_hot_posts(),
                                    expiretime=300)

def get_new_posts():
    return _new_posts

def get_drafts(user_id):
    s = select([_column.id, _column.title, _column.createDate, _column.updateDate, _column.section_id], _column.status==2)
    s = s.where(_column.user_id==user_id)
    s = s.order_by(_column.updateDate.desc())
    return s

def is_post_exist(post_id):
    return _posts_cache.has_key(post_id)

def get_post(post_id):
    return _posts_cache.get(post_id)

def get_post_from_db(post_id):
    return meta.Session.query(Post).filter(Post.id==post_id).first()

def update_post_viewTimes(post_id):
    meta.engine.execute(_table.update().where(_column.id==post_id).values(viewTimes=_column.viewTimes + 1))
    _posts_cache[post_id].viewTimes += 1
    
def delete_draft(post_id):
    post = get_post_from_db(post_id)
    meta.Session.delete(post)
    meta.Session.commit()
    return post.title

_comments_cache = {}  # key: post_id, value: list of CommentInfo sorted by createDate desc

class CommentInfo(object):

    def get_user(self):
        return user_service.get_user_by_id(self.user_id)

    user = property(get_user)

    def get_extra(self):
        userInfo = self.get_user()        
        return u'%s被 %s 评论'%(h.get_readable_date(self.createDate), h.get_user_link(userInfo))

    def __cmp__(self, c):
        return -cmp(self.createDate, c.createDate)


def _copy_comment(comment_info, comment):
    comment_info.id = comment.id
    comment_info.content = comment.content
    comment_info.createDate = comment.createDate
    comment_info.user_id = comment.user_id
    comment_info.post_id = comment.post_id

def get_comments(post_id):
    if not _comments_cache.has_key(post_id):
        _load_comments_from_db(post_id)

    return _comments_cache[post_id]

def _load_comments_from_db(post_id):
    q = meta.Session.query(Comment).filter(Comment.post_id==post_id)
    q = q.order_by(Comment.createDate.desc())
    _comments_cache[post_id] = []
    for comment in q.all():
        comment_info = CommentInfo()
        _copy_comment(comment_info, comment)
        _comments_cache[post_id].append(comment_info)
    return _comments_cache[post_id]
    
def add_comment_to_cache(comment):
    comment_info = CommentInfo()
    _copy_comment(comment_info, comment)
    if not _comments_cache.has_key(comment_info.post_id):
        _comments_cache[comment_info.post_id] = []
    insort_left(_comments_cache[comment_info.post_id], comment_info)

_promote_cache = {}  # key: post_id, value: set of identifier, ip or user name
_demote_cache = {} # key: post_id, value: set of identifier, ip or user name
_lastest_visit_cache = {} # key: post_id, value: lastest VisitInfo

class VisitInfo(object):

    def get_extra(self):
        userInfo = user_service.get_user(self.userIdentifier)
        if self.promote == 1:
            return u'%s被 %s 顶'%(h.get_readable_date(self.createDate), h.get_user_link(userInfo))
        else:
            return u'%s被 %s 踩'%(h.get_readable_date(self.createDate), h.get_user_link(userInfo))


def _copy_visit(visit_info, visit):
    visit_info.id = visit.id
    visit_info.post_id = visit.post_id
    visit_info.userIdentifier = visit.userIdentifier
    visit_info.promote = visit.promote
    visit_info.createDate = visit.createDate

def get_promote_count(post_id):
    if not _promote_cache.has_key(post_id):
        _load_visitor_trace_from_db(post_id)
    return len(_promote_cache[post_id])

def get_demote_count(post_id):
    if not _demote_cache.has_key(post_id):
        _load_visitor_trace_from_db(post_id)
    return len(_demote_cache[post_id])

def is_user_digged(post_id, user_identifier):
    if not _promote_cache.has_key(post_id):
        _load_visitor_trace_from_db(post_id)
    return user_identifier in _promote_cache[post_id] or user_identifier in _demote_cache[post_id]

def add_digg_to_cache(visit):
    _lastest_visit_cache[visit.post_id] = VisitInfo()
    _copy_visit(_lastest_visit_cache[visit.post_id], visit)
    if visit.promote == 1:
        _promote_cache[visit.post_id].add(visit.userIdentifier)
    else:
        _demote_cache[visit.post_id].add(visit.userIdentifier)

def get_latest_visit(post_id):
    if not _promote_cache.has_key(post_id):
        _load_visitor_trace_from_db(post_id)
    return _lastest_visit_cache.get(post_id)

def debug(content):
    s = "INSERT INTO debug_info(content, createDate) VALUES (?, ?)"
    return meta.engine.execute(s, content=content, createDate=h.now())

def _load_visitor_trace_from_db(post_id):
    q = meta.Session.query(VisitorTrace).filter(VisitorTrace.post_id==post_id).order_by(VisitorTrace.createDate.desc())
    _promote_cache[post_id] = set()
    _demote_cache[post_id] = set()
    for visit in q.all():
        if not _lastest_visit_cache.has_key(post_id):
            _lastest_visit_cache[post_id] = VisitInfo()
            _copy_visit(_lastest_visit_cache[post_id], visit)
        if visit.promote == 1:
            _promote_cache[visit.post_id].add(visit.userIdentifier)
        else:
            _demote_cache[visit.post_id].add(visit.userIdentifier)

def init_cache():
    global initailized
    if not initailized:
        initailized = True
        posts = meta.Session.query(Post).filter(Post.status==1).order_by(Post.createDate.desc()).all()
        for post in posts:
            post_info = PostInfo() 
            _posts_cache[post.id] = post_info
            _copy_post(post_info, post)
            _new_posts.append(post_info)
            if not _section_first_post.has_key(post_info.section_id):
                _section_first_post[post_info.section_id] = post_info

init_cache()